package com.cellngine.server;

import java.util.ArrayList;
import java.util.Iterator;

import com.cellngine.Constants;
import com.cellngine.JO;

/**
 * ServerClient
 * 
 * Describes a connected, authorized client. Messages can be exchanged with a client
 * simultaneously over multiple connections to make better use of the available bandwidth.
 * 
 * @author Felix Kirchmann <qwer@cellngine.com>
 */
public class ServerClient
{
	private final ServerManager				master;
	private ArrayList<ServerClientSocket>	sockets;
	private int								currentSocketIndex	= 0;
	private int								id					= -1;
	private byte[]							sessionID;
	private String							ip;
	
	public ServerClient(final ServerManager master, final int id)
	{
		this.master = master;
		sessionID = JO.generateRandomBytes(Constants.session_id_length_bytes);
		this.id = id;
	}
	
	/**
	 * @return This client's IP address, or <code>null</code> if no connections to the client are
	 *         known.
	 */
	public String getIP()
	{
		return ip;
	}
	
	/**
	 * @return This client's ID. ID's lower than 0 generally mean that this client is not fully
	 *         connected.
	 */
	public int getID()
	{
		return id;
	}
	
	/**
	 * @return This client's session ID, which changes each time a client (re)connects. session ID's
	 *         equalling <code>null</code> generally mean that this client is not fully connected.
	 */
	public byte[] getSessionID()
	{
		return sessionID;
	}
	
	/**
	 * Adds a Socket to the internal list.
	 * 
	 * @param sock
	 *            A <code>ServerClientSocket</code> Which is connected to the client represented by
	 *            this object and is ready to exchange data.
	 * @throws <code>NullPointerException</code> if <code>socket == null</code>
	 * @throws <code>IllegalArgumentException</code> if <code>socket.ip</code> is different from
	 *         the first socket's client ip.
	 */
	public synchronized void addSocket(final ServerClientSocket socket)
	{
		if (socket == null)
		{
			throw new NullPointerException();
		}
		if (sockets == null)
		{
			sockets = new ArrayList<ServerClientSocket>();
			this.ip = socket.ip;
		}
		if (socket.ip != this.ip)
		{
			throw new IllegalArgumentException();
		}
		sockets.add(socket);
	}
	
	/**
	 * Closes all connections to the client and deactivates it.
	 */
	public synchronized void disconnect()
	{
		closeConnections();
		deactivate();
	}
	
	/**
	 * Closes all connections to the client, but does not remove the client itself from the
	 * ServerManager's list.
	 */
	public synchronized void closeConnections()
	{
		final Iterator<ServerClientSocket> iterator = sockets.iterator();
		while (iterator.hasNext())
		{
			iterator.next().close();
		}
		sockets = null;
	}
	
	/**
	 * Checks if all connections are active and removes broken ones. If no working connections can
	 * be found, this client will be deactivated.
	 * 
	 * @return Whether or not data can be exchanged with this client.
	 */
	public synchronized boolean isConnected()
	{
		if (sockets == null || sockets.size() == 0)
		{
			return false;
		}
		final Iterator<ServerClientSocket> iterator = sockets.iterator();
		while (iterator.hasNext())
		{
			if (iterator.next().connectionAlive())
			{
				return true;
			}
			else
			{
				iterator.remove();
			}
		}
		deactivate();
		return false;
	}
	
	/**
	 * @return The number of available connections through which data can be exchanged.
	 */
	public int activeConnections()
	{
		if (!isConnected())
		{
			return 0;
		}
		return sockets.size();
	}
	
	/**
	 * Sends a raw message to this client.
	 * 
	 * @param message
	 *            A String representation of the message which shall be sent.
	 * @return <code>true</code> if the message was sent successfully, otherwise <code>false</code>.
	 */
	public synchronized boolean sendMessage(final String message)
	{
		if (!isConnected())
		{
			return false;
		}
		if (currentSocketIndex >= sockets.size())
		{
			currentSocketIndex = 0;
		}
		final boolean status = sockets.get(currentSocketIndex).send(message);
		currentSocketIndex++;
		return status;
	}
	
	private synchronized void deactivate()
	{
		master.removeClient(id);
		id = -1;
		sessionID = null;
	}
}
